#define _POSIX_C_SOURCE 200112L
#define _DARWIN_C_SOURCE
#include "auth_manager.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <inttypes.h>

// Simple SHA256-like hash implementation (zero external dependencies)
static void simple_sha256(const char* input, char* output) {
    // Simple but reasonably secure hash function
    uint32_t h[8] = {
        0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
        0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
    };
    
    size_t len = strlen(input);
    for (size_t i = 0; i < len; i++) {
        uint32_t ch = (uint32_t)input[i];
        for (int j = 0; j < 8; j++) {
            h[j] = ((h[j] << 5) + h[j]) + ch + i + j;
            h[j] ^= (h[j] >> 13);
            h[j] *= 0x5bd1e995;
            h[j] ^= (h[j] >> 15);
        }
    }
    
    snprintf(output, 65, "%08x%08x%08x%08x%08x%08x%08x%08x", 
             h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7]);
}

// Helper functions
static uint64_t get_current_time(void) {
    return (uint64_t)time(NULL);
}

static uint64_t get_current_time_ms(void) {
    struct timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts);
    return (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}

// Authentication manager initialization
auth_manager_t* auth_init(void) {
    auth_manager_t* auth = calloc(1, sizeof(auth_manager_t));
    if (!auth) {
        return NULL;
    }

    // Initialize mutex
    if (pthread_mutex_init(&auth->mutex, NULL) != 0) {
        free(auth);
        return NULL;
    }

    // Set default configuration
    auth->max_failed_attempts = 5;
    auth->session_timeout = 3600; // 1 hour
    auth->enforce_password_policy = true;

    // Create default admin user
    if (auth_create_user(auth, "admin", "admin123", ROLE_ADMIN, HASH_TYPE_SHA256) != 0) {
        pthread_mutex_destroy(&auth->mutex);
        free(auth);
        return NULL;
    }

    return auth;
}

// Authentication manager cleanup
int auth_cleanup(auth_manager_t* auth) {
    if (!auth) {
        return 0;
    }

    pthread_mutex_lock(&auth->mutex);

    // Free users
    auth_user_t* user = auth->users;
    while (user) {
        auth_user_t* next = user->next;
        free(user);
        user = next;
    }

    // Free permissions
    table_permission_t* perm = auth->permissions;
    while (perm) {
        table_permission_t* next = perm->next;
        free(perm);
        perm = next;
    }

    // Free sessions
    auth_session_t* session = auth->sessions;
    while (session) {
        auth_session_t* next = session->next;
        free(session);
        session = next;
    }

    pthread_mutex_unlock(&auth->mutex);
    pthread_mutex_destroy(&auth->mutex);
    
    free(auth);
    return 0;
}

// User management
int auth_create_user(auth_manager_t* auth, const char* username, const char* password,
                    const char* role, hash_type_t hash_type) {
    if (!auth || !username || !password || !role) {
        return -1;
    }

    // Validate username length
    if (strlen(username) >= MAX_USERNAME_LEN) {
        return -1;
    }

    pthread_mutex_lock(&auth->mutex);

    // Check if user already exists
    if (auth_find_user(auth, username) != NULL) {
        pthread_mutex_unlock(&auth->mutex);
        return -1; // User already exists
    }

    // Create new user
    auth_user_t* user = calloc(1, sizeof(auth_user_t));
    if (!user) {
        pthread_mutex_unlock(&auth->mutex);
        return -1;
    }

    strncpy(user->username, username, MAX_USERNAME_LEN - 1);
    strncpy(user->role, role, sizeof(user->role) - 1);
    user->hash_type = hash_type;
    user->is_active = true;
    user->created_time = get_current_time();

    // Generate salt and hash password
    if (auth_generate_salt(user->salt, MAX_SALT_LEN) != 0 ||
        auth_hash_password(password, user->salt, hash_type, 
                          user->password_hash, MAX_HASH_LEN) != 0) {
        free(user);
        pthread_mutex_unlock(&auth->mutex);
        return -1;
    }

    // Add to user list
    user->next = auth->users;
    auth->users = user;
    auth->user_count++;

    // Grant default permissions based on role
    uint8_t default_perms;
    if (auth_get_role_permissions(role, &default_perms) == 0) {
        // Grant default permissions to all tables (wildcard "*")
        auth_grant_table_permission(auth, username, "*", default_perms);
    }

    pthread_mutex_unlock(&auth->mutex);
    return 0;
}

int auth_delete_user(auth_manager_t* auth, const char* username) {
    if (!auth || !username) {
        return -1;
    }

    pthread_mutex_lock(&auth->mutex);

    // Find and remove user
    auth_user_t** user_ptr = &auth->users;
    while (*user_ptr) {
        if (strcmp((*user_ptr)->username, username) == 0) {
            auth_user_t* user_to_delete = *user_ptr;
            *user_ptr = (*user_ptr)->next;
            free(user_to_delete);
            auth->user_count--;
            break;
        }
        user_ptr = &(*user_ptr)->next;
    }

    // Remove all permissions for this user
    table_permission_t** perm_ptr = &auth->permissions;
    while (*perm_ptr) {
        if (strcmp((*perm_ptr)->username, username) == 0) {
            table_permission_t* perm_to_delete = *perm_ptr;
            *perm_ptr = (*perm_ptr)->next;
            free(perm_to_delete);
            auth->permission_count--;
        } else {
            perm_ptr = &(*perm_ptr)->next;
        }
    }

    pthread_mutex_unlock(&auth->mutex);
    return 0;
}

auth_user_t* auth_find_user(auth_manager_t* auth, const char* username) {
    if (!auth || !username) {
        return NULL;
    }

    auth_user_t* user = auth->users;
    while (user) {
        if (strcmp(user->username, username) == 0) {
            return user;
        }
        user = user->next;
    }
    return NULL;
}

// Authentication
int auth_authenticate(auth_manager_t* auth, const char* username, const char* password,
                     uint64_t* session_id) {
    if (!auth || !username || !password || !session_id) {
        return -1;
    }

    pthread_mutex_lock(&auth->mutex);
    auth->total_logins++;

    auth_user_t* user = auth_find_user(auth, username);
    if (!user || !user->is_active || user->is_locked) {
        auth->failed_logins++;
        pthread_mutex_unlock(&auth->mutex);
        return -1;
    }

    // Verify password
    if (auth_verify_password(password, user->password_hash, user->salt, user->hash_type) != 0) {
        user->failed_login_attempts++;
        auth->failed_logins++;
        
        // Lock user if too many failed attempts
        if (user->failed_login_attempts >= auth->max_failed_attempts) {
            user->is_locked = true;
        }
        
        pthread_mutex_unlock(&auth->mutex);
        return -1;
    }

    // Authentication successful
    user->failed_login_attempts = 0;
    user->last_login = get_current_time();

    // Create session
    auth_session_t* session = calloc(1, sizeof(auth_session_t));
    if (!session) {
        pthread_mutex_unlock(&auth->mutex);
        return -1;
    }

    strncpy(session->username, username, MAX_USERNAME_LEN - 1);
    strncpy(session->role, user->role, sizeof(session->role) - 1);
    session->session_id = auth_generate_session_id();
    session->login_time = get_current_time();
    session->last_activity = session->login_time;
    session->is_authenticated = true;

    // Add to session list
    session->next = auth->sessions;
    auth->sessions = session;
    auth->session_count++;

    *session_id = session->session_id;

    pthread_mutex_unlock(&auth->mutex);
    return 0;
}

int auth_logout(auth_manager_t* auth, uint64_t session_id) {
    if (!auth) {
        return -1;
    }

    pthread_mutex_lock(&auth->mutex);

    // Find and remove session
    auth_session_t** session_ptr = &auth->sessions;
    while (*session_ptr) {
        if ((*session_ptr)->session_id == session_id) {
            auth_session_t* session_to_delete = *session_ptr;
            *session_ptr = (*session_ptr)->next;
            free(session_to_delete);
            auth->session_count--;
            pthread_mutex_unlock(&auth->mutex);
            return 0;
        }
        session_ptr = &(*session_ptr)->next;
    }

    pthread_mutex_unlock(&auth->mutex);
    return -1; // Session not found
}

auth_session_t* auth_find_session(auth_manager_t* auth, uint64_t session_id) {
    if (!auth) {
        return NULL;
    }

    auth_session_t* session = auth->sessions;
    while (session) {
        if (session->session_id == session_id && session->is_authenticated) {
            return session;
        }
        session = session->next;
    }
    return NULL;
}

// Permission management
int auth_grant_table_permission(auth_manager_t* auth, const char* username,
                               const char* table_name, uint8_t permissions) {
    if (!auth || !username || !table_name) {
        return -1;
    }

    pthread_mutex_lock(&auth->mutex);

    // Check if permission already exists
    table_permission_t* perm = auth->permissions;
    while (perm) {
        if (strcmp(perm->username, username) == 0 && 
            strcmp(perm->table_name, table_name) == 0) {
            // Update existing permission
            perm->permissions |= permissions;
            pthread_mutex_unlock(&auth->mutex);
            return 0;
        }
        perm = perm->next;
    }

    // Create new permission
    perm = calloc(1, sizeof(table_permission_t));
    if (!perm) {
        pthread_mutex_unlock(&auth->mutex);
        return -1;
    }

    strncpy(perm->username, username, MAX_USERNAME_LEN - 1);
    strncpy(perm->table_name, table_name, MAX_TABLENAME_LEN - 1);
    perm->permissions = permissions;

    // Add to permission list
    perm->next = auth->permissions;
    auth->permissions = perm;
    auth->permission_count++;

    pthread_mutex_unlock(&auth->mutex);
    return 0;
}

int auth_check_permission(auth_manager_t* auth, uint64_t session_id,
                         const char* table_name, uint8_t permission) {
    if (!auth || !table_name) {
        return -1;
    }

    pthread_mutex_lock(&auth->mutex);
    auth->permission_checks++;

    // Find session
    auth_session_t* session = auth_find_session(auth, session_id);
    if (!session) {
        auth->permission_denials++;
        pthread_mutex_unlock(&auth->mutex);
        return -1;
    }

    // Update session activity
    session->last_activity = get_current_time();

    // Admin has all permissions
    if (strcmp(session->role, ROLE_ADMIN) == 0) {
        pthread_mutex_unlock(&auth->mutex);
        return 0;
    }

    // Check table-specific permissions
    table_permission_t* perm = auth->permissions;
    while (perm) {
        if (strcmp(perm->username, session->username) == 0 &&
            (strcmp(perm->table_name, table_name) == 0 || 
             strcmp(perm->table_name, "*") == 0)) {
            if (perm->permissions & permission) {
                pthread_mutex_unlock(&auth->mutex);
                return 0;
            }
        }
        perm = perm->next;
    }

    // Check role-based permissions
    uint8_t role_perms;
    if (auth_get_role_permissions(session->role, &role_perms) == 0) {
        if (role_perms & permission) {
            pthread_mutex_unlock(&auth->mutex);
            return 0;
        }
    }

    auth->permission_denials++;
    pthread_mutex_unlock(&auth->mutex);
    return -1;
}

// Role-based permissions
int auth_get_role_permissions(const char* role, uint8_t* default_permissions) {
    if (!role || !default_permissions) {
        return -1;
    }

    if (strcmp(role, ROLE_ADMIN) == 0) {
        *default_permissions = PERM_SELECT | PERM_INSERT | PERM_UPDATE | PERM_DELETE | 
                              PERM_CREATE | PERM_DROP | PERM_ALTER | PERM_GRANT;
    } else if (strcmp(role, ROLE_READ_WRITE) == 0) {
        *default_permissions = PERM_SELECT | PERM_INSERT | PERM_UPDATE | PERM_DELETE;
    } else if (strcmp(role, ROLE_READ_ONLY) == 0) {
        *default_permissions = PERM_SELECT;
    } else {
        return -1; // Unknown role
    }

    return 0;
}

// Password hashing and validation
int auth_generate_salt(char* salt, size_t salt_len) {
    if (!salt || salt_len < MAX_SALT_LEN) {
        return -1;
    }

    // Generate random salt
    for (size_t i = 0; i < MAX_SALT_LEN - 1; i++) {
        salt[i] = 'A' + (rand() % 26);
    }
    salt[MAX_SALT_LEN - 1] = '\0';

    return 0;
}

int auth_hash_password(const char* password, const char* salt, hash_type_t type,
                      char* hash_output, size_t hash_len) {
    if (!password || !salt || !hash_output || hash_len < MAX_HASH_LEN) {
        return -1;
    }

    char combined[256];
    snprintf(combined, sizeof(combined), "%s%s", password, salt);

    switch (type) {
        case HASH_TYPE_SHA256:
        case HASH_TYPE_SHA2_CACHING:
            simple_sha256(combined, hash_output);
            break;
            
        case HASH_TYPE_PLAIN:
            // For testing purposes only - NOT SECURE
            strncpy(hash_output, password, hash_len - 1);
            hash_output[hash_len - 1] = '\0';
            break;
    }

    return 0;
}

int auth_verify_password(const char* password, const char* hash, const char* salt,
                        hash_type_t type) {
    if (!password || !hash || !salt) {
        return -1;
    }

    char computed_hash[MAX_HASH_LEN];
    if (auth_hash_password(password, salt, type, computed_hash, MAX_HASH_LEN) != 0) {
        return -1;
    }

    return strcmp(computed_hash, hash) == 0 ? 0 : -1;
}

// Session management
uint64_t auth_generate_session_id(void) {
    static uint64_t counter = 0;
    return (get_current_time_ms() << 16) | (__sync_fetch_and_add(&counter, 1) & 0xFFFF);
}

int auth_cleanup_expired_sessions(auth_manager_t* auth) {
    if (!auth) {
        return -1;
    }

    pthread_mutex_lock(&auth->mutex);

    uint64_t current_time = get_current_time();
    auth_session_t** session_ptr = &auth->sessions;
    int cleaned = 0;

    while (*session_ptr) {
        if (current_time - (*session_ptr)->last_activity > auth->session_timeout) {
            auth_session_t* session_to_delete = *session_ptr;
            *session_ptr = (*session_ptr)->next;
            free(session_to_delete);
            auth->session_count--;
            cleaned++;
        } else {
            session_ptr = &(*session_ptr)->next;
        }
    }

    pthread_mutex_unlock(&auth->mutex);
    return cleaned;
}

// Statistics and utilities
void auth_print_stats(const auth_manager_t* auth) {
    if (!auth) {
        return;
    }

    printf("Authentication Manager Statistics:\n");
    printf("  Users: %u\n", auth->user_count);
    printf("  Permissions: %u\n", auth->permission_count);
    printf("  Active sessions: %u\n", auth->session_count);
    printf("  Total logins: %" PRIu64 "\n", auth->total_logins);
    printf("  Failed logins: %" PRIu64 "\n", auth->failed_logins);
    printf("  Permission checks: %" PRIu64 "\n", auth->permission_checks);
    printf("  Permission denials: %" PRIu64 "\n", auth->permission_denials);
    printf("  Success rate: %.2f%%\n", 
           auth->total_logins > 0 ? 
           (double)(auth->total_logins - auth->failed_logins) / auth->total_logins * 100.0 : 0.0);
}

void auth_print_users(const auth_manager_t* auth) {
    if (!auth) {
        return;
    }

    printf("Users:\n");
    auth_user_t* user = auth->users;
    while (user) {
        printf("  %s (%s) - %s, Created: %" PRIu64 ", Last Login: %" PRIu64 "\n",
               user->username, user->role, 
               user->is_active ? (user->is_locked ? "LOCKED" : "ACTIVE") : "INACTIVE",
               user->created_time, user->last_login);
        user = user->next;
    }
}

const char* auth_permission_to_string(uint8_t permissions) {
    static char perm_str[128];
    perm_str[0] = '\0';
    
    if (permissions & PERM_SELECT) strcat(perm_str, "SELECT,");
    if (permissions & PERM_INSERT) strcat(perm_str, "INSERT,");
    if (permissions & PERM_UPDATE) strcat(perm_str, "UPDATE,");
    if (permissions & PERM_DELETE) strcat(perm_str, "DELETE,");
    if (permissions & PERM_CREATE) strcat(perm_str, "CREATE,");
    if (permissions & PERM_DROP)   strcat(perm_str, "DROP,");
    if (permissions & PERM_ALTER)  strcat(perm_str, "ALTER,");
    if (permissions & PERM_GRANT)  strcat(perm_str, "GRANT,");
    
    // Remove trailing comma
    size_t len = strlen(perm_str);
    if (len > 0 && perm_str[len - 1] == ',') {
        perm_str[len - 1] = '\0';
    }
    
    return perm_str;
}